Skip to content

th-d60a73: gated interleaved tool-activity chips in chat-widget#29

Merged
brentrager merged 1 commit into
mainfrom
th-d60a73-chat-widget-tool-activity
Jul 9, 2026
Merged

th-d60a73: gated interleaved tool-activity chips in chat-widget#29
brentrager merged 1 commit into
mainfrom
th-d60a73-chat-widget-tool-activity

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

The embeddable @smooai/chat-widget only grew a prose bubble from stream_token — the agent's tool activity streaming over the smooth-operator protocol as stream_chunk events was invisible (send()'s loop default: break'd on it). The smooth daemon SPA already renders interleaved tool chips via its blocks model; this ports that to the widget.

What changed — and how the gate works

Because this is a customer-facing widget, exposing raw tool calls (grep / bash / read_file) to a support-site end-user is usually undesirable. So the feature is gated behind showToolActivity (default false):

  • false (default): behavior is byte-for-byte unchanged — the assistant streams a single prose bubble, no blocks are built, stream_chunk is ignored exactly as before.
  • true: a turn that invokes a tool renders as an ordered strip of prose bubbles + inline tool chips (running… / done ✓ / error), in the order the model produced them. Enable via the show-tool-activity HTML attribute or showToolActivity: true in the config object.

Wiring:

  • config.ts — new showToolActivity option, defaulted false in resolveConfig.
  • conversation.ts — new MessageBlock/ToolCall types + blocks? on ChatMessage; send() folds stream_token into a trailing text block and stream_chunk tool activity into the block list (only when gated). Prose-only turns drop blocks on finalize so they render via the normal markdown path.
  • element.ts / styles.tsrenderAssistantBlocks + ToolChip, reusing the existing rAF reveal for the live trailing text block; chip styles keyed to the theme tokens.

Correctness / security

  • Tool activity is read from state.rawResponse.toolCall / state.rawResponse.toolResult — the correct nested path. Reading state.toolResult (one level too high) leaves chips stuck "running…"; a test asserts the wrong-path payload does not resolve a chip.
  • Tool name + args preview are rendered with textContent (never innerHTML), so a tool payload can't inject markup.

Protocol notes

  • The single for await turn loop interleaves stream_token (prose) and stream_chunk (tools) in arrival order; concurrent tools sharing a name resolve most-recent-open-first (matches the daemon).
  • The rAF typewriter now binds to the live trailing text block for a tool turn (composite reveal key), and to the whole message for a plain-prose turn — unchanged for the default path.

Tests / build

pnpm typecheck clean; pnpm test 68 pass (10 new across gate default, interleave, error, wrong-path guard, and chip rendering); pnpm build green. New public MessageBlock/ToolCall exports; README config table updated.

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Jul 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7b5ed8e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@smooai/chat-widget Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Add an opt-in showToolActivity option that renders the agent's tool
activity as inline chips interleaved with its prose, mirroring the smooth
daemon SPA's blocks model.

- Defaults to false: the customer-facing widget is byte-for-byte unchanged
  (prose bubble only). Enable via show-tool-activity attr / showToolActivity.
- When on, a tool-invoking turn renders an ordered strip of prose bubbles +
  tool chips (running…/done/error). Tools read from
  state.rawResponse.toolCall/.toolResult (correct nested path — reading
  state.toolResult leaves chips stuck 'running…').
- Tool name/args via textContent (no innerHTML) — payloads can't inject markup.
- New public MessageBlock/ToolCall types; tests for the gate, interleave,
  wrong-path guard, and chip rendering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@brentrager brentrager force-pushed the th-d60a73-chat-widget-tool-activity branch from 3346eb3 to 7b5ed8e Compare July 9, 2026 00:31
@brentrager brentrager merged commit 92b31ad into main Jul 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant